package com.base.components.common.service.sensitive;

import com.base.components.common.boot.SpringContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * SensitiveWord 初始化敏感词库，将敏感词加入到HashMap中，构建DFA算法模型；
 *
 * @author <a href="drakelee1221@gmail.com">LiGeng</a>
 * @version v1.0.0
 * @date 2018-12-18 11:39
 */
public class SensitiveWord {
  private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  private static final String SENSITIVE_FILE_PATH = "sensitive_word.txt";
  private static volatile SensitiveWord INSTANCE;
  private static final int DEFAULT_DISTANCE = 2;
  private SensitiveUpdateKeyCache sensitiveUpdateKeyCache;
  private AtomicBoolean checking = new AtomicBoolean(false);
  /** 用于比对是否重新加载的标识 */
  private volatile Serializable updateKey;
  /** 只读 Map */
  private volatile WordsMap sensitiveWordMap;

  private SensitiveWord() {
    this.sensitiveUpdateKeyCache = getSensitiveUpdateKeyCache();
    reloadSensitiveWord();
  }

  /** 在程序启动完成后调用 */
  public static SensitiveWord instance() {
    if (INSTANCE == null) {
      synchronized (SensitiveWord.class) {
        if (INSTANCE == null) {
          INSTANCE = new SensitiveWord();
        }
      }
    }
    return INSTANCE;
  }
  /**
   * 替换敏感词，默认干扰间距2个字符
   *
   * @param src 输入文本
   * @param replace 替换符号
   */
  public String replace(String src, char replace) {
    return replace(src, replace, DEFAULT_DISTANCE);
  }

  /**
   * 替换敏感词
   *
   * @param src 输入文本
   * @param replace 替换符号
   * @param distance 文本干扰间距，如：0=匹配张三，1=匹配张三，张x三，2=匹配张三，张x三，张xx三
   */
  public String replace(String src, char replace, int distance) {
    char[] charset = src.toCharArray();
    for (int i = 0; i < charset.length; i++) {
      List<Integer> indices = getFlagIndex(charset, i, distance);
      if (indices != null) {
        for (int j : indices) {
          charset[j] = replace;
        }
      }
    }
    checkUpdateKey();
    return new String(charset);
  }

  /**
   * 获取敏感词列表，默认干扰间距2个字符
   *
   * @param src 输入文本
   */
  public Set<String> sensitiveList(String src) {
    return sensitiveList(src, DEFAULT_DISTANCE);
  }

  /**
   * 获取敏感词列表
   *
   * @param src 输入文本
   * @param distance 文本干扰间距，如：0=匹配张三，1=匹配张三，张x三，2=匹配张三，张x三，张xx三
   */
  public Set<String> sensitiveList(String src, int distance) {
    Set<String> sensitives = new HashSet<>();
    char[] charset = src.toCharArray();
    for (int i = 0; i < charset.length; i++) {
      List<Integer> indices = getFlagIndex(charset, i, distance);
      if (indices != null) {
        StringBuilder buffer = new StringBuilder();
        for (int j : indices) {
          char word = src.charAt(j);
          buffer.append(word);
        }
        sensitives.add(buffer.toString());
      }
    }
    checkUpdateKey();
    return sensitives;
  }

  /**
   * 重新加载全部敏感词，并通知更新 updateKey
   */
  public void reloadSensitiveWord() {
    if(sensitiveUpdateKeyCache != null){
      this.updateKey = UUID.randomUUID().toString().replace("-","");
      sensitiveUpdateKeyCache.cacheUpdateKey(this.updateKey);
    }
    Set<String> words = loadSensitiveWordFile();
    this.sensitiveWordMap = buildSensitiveWordMap(new WordsMap(words.size()), words);
  }

  /**
   * 获取标记索引
   *
   * @param charset 输入文本
   * @param begin 检测起始
   * @param distance 文本距离
   */
  private List<Integer> getFlagIndex(char[] charset, int begin, int distance) {
    WordsMap nowMap = sensitiveWordMap;
    boolean flag = false;
    if (distance < 0) {
      distance = 0;
    }
    int count = 0;
    List<Integer> index = null;
    for (int i = begin; i < charset.length; i++) {
      char word = charset[i];
      WordsMap mapTree = nowMap.get(word);
      if (count > distance || (i == begin && Objects.isNull(mapTree))) {
        break;
      }
      if (!Objects.isNull(mapTree)) {
        nowMap = mapTree;
        count = 0;
        if (index == null){
          index = new ArrayList<>();
        }
        index.add(i);
      } else {
        count++;
      }
      if (nowMap.getEnd()) {
        flag = true;
        break;
      }
    }
    return flag ? index : null;
  }

  /**
   * 读取敏感词库，将敏感词放入Map中，构建一个DFA算法模型：<br>
   */
  private WordsMap buildSensitiveWordMap(WordsMap currentMap, Set<String> sensitiveWords) {
    WordsMap nowMap;
    WordsMap newWordMap;
    for (String key : sensitiveWords) {
      nowMap = currentMap;
      for (int i = 0; i < key.length(); i++) {
        char keyChar = key.charAt(i);
        Object wordMap = nowMap.get(keyChar);
        // 如果存在该key，直接赋值
        if (wordMap != null) {
          nowMap = (WordsMap) wordMap;
        }
        // 不存在则，则构建一个map，同时将end设置为false，因为他不是最后一个
        else {
          newWordMap = new WordsMap();
          // 不是最后一个
          newWordMap.setEnd(false);
          nowMap.put(keyChar, newWordMap);
          nowMap = newWordMap;
        }
        if (i == key.length() - 1) {
          // 最后一个
          nowMap.setEnd(true);
        }
      }
    }
    return currentMap;
  }

  private Set<String> loadSensitiveWordFile() {
    try {
      SensitiveResourceLoader loader = SpringContextUtil.getBean(SensitiveResourceLoader.class);
      return loader.loadSensitiveWords();
    } catch (Exception e) {
      logger.warn("fail to load sensitive words from bean SensitiveResourceLoader, cause: "+e.getMessage());
    }
    Set<String> words = new HashSet<>();
    try (InputStream inputStream = SensitiveWord.class.getClassLoader().getResourceAsStream(SENSITIVE_FILE_PATH);
         InputStreamReader read = new InputStreamReader(inputStream, "UTF-8")) {
      BufferedReader bufferedReader = new BufferedReader(read);
      String txt;
      while ((txt = bufferedReader.readLine()) != null) {
        words.add(txt);
      }
    } catch (Exception e) {
      throw new RuntimeException("fail to load " + SENSITIVE_FILE_PATH, e);
    }
    return words;
  }

  private SensitiveUpdateKeyCache getSensitiveUpdateKeyCache(){
    try {
      return SpringContextUtil.getBean(SensitiveUpdateKeyCache.class);
    } catch (Exception e) {
      return null;
    }
  }

  private void checkUpdateKey(){
    if(sensitiveUpdateKeyCache != null && !checking.get()){
      checking.set(true);
      CompletableFuture.runAsync(() -> {
        try {
          Serializable cacheUpdateKey = sensitiveUpdateKeyCache.getCacheUpdateKey();
          // 对比本地key，不一致则为被通知重新加载
          if(cacheUpdateKey != null && !cacheUpdateKey.equals(this.updateKey)){
            this.updateKey = cacheUpdateKey;
            sensitiveUpdateKeyCache.cacheUpdateKey(this.updateKey);
            Set<String> words = loadSensitiveWordFile();
            this.sensitiveWordMap = buildSensitiveWordMap(new WordsMap(words.size()), words);
          }
        }catch (Exception ignore){
        }finally {
          checking.set(false);
        }
      });
    }
  }

  private static class WordsMap extends HashMap<Character, WordsMap>{
    private static final long serialVersionUID = -5236186160040588804L;
    private WordsMap(){}
    private WordsMap(int initialCapacity) {
      super(initialCapacity);
    }
    /** 是否末尾字符 */
    private boolean end;
    private void setEnd(boolean end) {
      this.end = end;
    }
    private boolean getEnd() {
      return end;
    }
  }

//  public static void main(String[] args) throws Exception {
//    StringBuilder sb = new StringBuilder();
//    try (InputStream inputStream = SensitiveWord.class.getClassLoader().getResourceAsStream("A.txt");
//         InputStreamReader read = new InputStreamReader(inputStream, "UTF-8")) {
//      BufferedReader bufferedReader = new BufferedReader(read);
//      String txt;
//      while ((txt = bufferedReader.readLine()) != null) {
//        sb.append(txt);
//      }
//    }
//    SensitiveWord instance = instance();
//    long b = System.currentTimeMillis();
//    CountDownLatch cdl = new CountDownLatch(100);
//    for (int i = 0; i < 100; i++) {
//      new Thread(() -> {
//        instance.replace(sb.toString(), '*');
//        cdl.countDown();
//      }).start();
//    }
//    cdl.await();
//    System.out.println(instance.checking);
//    System.out.println(System.currentTimeMillis() - b);
//  }
}
